package com.company.nuwa.mybatis.generator.plugins;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.text.CaseUtils;
import org.mybatis.generator.api.*;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.config.PropertyRegistry;
import org.mybatis.generator.internal.util.StringUtility;

import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;

/**
 * <p>生成额外java文件</p>
 *
 * @author Lionel Lee
 * @version 1.0: ServiceLayerPlugin.java
 * @date 2021/5/4  11:41 星期二
 */
public class DaoPlugin extends PluginAdapter {

    public static void selectByIdsMethod(IntrospectedTable introspectedTable,
                                         String domainObjectName, TopLevelClass requestClass) {
        IntrospectedColumn primaryColumn = introspectedTable.getPrimaryKeyColumns().stream()
            .findFirst().get();
        String columnName = StringUtils
            .capitalize(CaseUtils.toCamelCase(primaryColumn.getActualColumnName(), false, '_'));
        /**
         * add方法
         *  @PostMapping("/api/{domainObjectName}/add")
         *  HttpMessageResult<Boolean> add(@RequestBody @Validated DomainAddRequest request);
         */
        Method method = new Method("selectByIds");
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(
            new FullyQualifiedJavaType(MessageFormat.format("List<{0}>", domainObjectName)));
        method.addJavaDocLine("/**");
        method.addJavaDocLine("* 批量查询");
        method.addJavaDocLine("* @param ids 请求参数");
        method.addJavaDocLine("* @return Boolean");
        method.addJavaDocLine(" */");
        Parameter selectdParameter = new Parameter(new FullyQualifiedJavaType("List<Long>"), "ids");
        method.addParameter(selectdParameter);
        method.addAnnotation("@Generated(\"org.mybatis.generator.api.MyBatisGenerator\")");
        method.addBodyLine("if(CollectionUtils.isEmpty(ids)){");
        method.addBodyLine("return new ArrayList<>();");
        method.addBodyLine("}");

        method.addBodyLine(
            MessageFormat.format("{0}Example example = new {0}Example();", domainObjectName));
        method.addBodyLine(
            MessageFormat.format("example.createCriteria().and{0}In(ids);", columnName));
        method.addBodyLine("return mapper.selectByExample(example);");
        requestClass.addMethod(method);
    }

    public void geTableMethod(IntrospectedTable introspectedTable, TopLevelClass daoClass) {
        Method method = new Method("getTableName");
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(FullyQualifiedJavaType.getStringInstance());
        method.addAnnotation("@Generated(\"org.mybatis.generator.api.MyBatisGenerator\")");
        method.addAnnotation("@Override");
        method.addBodyLine(
            "return " + "\"" + introspectedTable.getFullyQualifiedTable().getIntrospectedTableName()
                           + "\";");
        daoClass.addMethod(method);
    }

    public void insertMethod(IntrospectedTable introspectedTable, String domainObjectName,
                             TopLevelClass daoClass) {
        Method method = new Method("insert");
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(FullyQualifiedJavaType.getIntInstance());
        Parameter parameter = new Parameter(new FullyQualifiedJavaType(domainObjectName), "record");
        method.addParameter(parameter);
        method.addAnnotation("@Generated(\"org.mybatis.generator.api.MyBatisGenerator\")");
        method.addAnnotation("@Override");
        method.addBodyLine(" return super.insert(record);");
        daoClass.addMethod(method);
    }

    public void updateMethod(IntrospectedTable introspectedTable, String domainObjectName,
                             TopLevelClass daoClass) {
        Method method = new Method("update");
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(FullyQualifiedJavaType.getIntInstance());
        Parameter parameter = new Parameter(new FullyQualifiedJavaType(domainObjectName), "record");
        method.addParameter(parameter);
        method.addAnnotation("@Generated(\"org.mybatis.generator.api.MyBatisGenerator\")");
        method.addAnnotation("@Override");
        method.addBodyLine(" return super.update(record);");
        daoClass.addMethod(method);
    }

    public void batchInsertMethod(IntrospectedTable introspectedTable, String domainObjectName,
                                  TopLevelClass daoClass) {
        Method method = new Method("batchInsert");
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(FullyQualifiedJavaType.getIntInstance());
        Parameter parameter = new Parameter(
            new FullyQualifiedJavaType("List<" + domainObjectName + ">"), "records");
        method.addParameter(parameter);
        method.addAnnotation("@Generated(\"org.mybatis.generator.api.MyBatisGenerator\")");
        method.addAnnotation("@Override");
        method.addBodyLine(" return super.batchInsert(records);");
        daoClass.addMethod(method);
    }

    public void batchUpdateMethod(IntrospectedTable introspectedTable, String domainObjectName,
                                  TopLevelClass daoClass) {
        Method method = new Method("batchUpdate");
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(FullyQualifiedJavaType.getIntInstance());
        Parameter parameter = new Parameter(
            new FullyQualifiedJavaType("List<" + domainObjectName + ">"), "records");
        method.addParameter(parameter);
        method.addAnnotation("@Generated(\"org.mybatis.generator.api.MyBatisGenerator\")");
        method.addAnnotation("@Override");
        method.addBodyLine(" return super.batchUpdate(records);");
        daoClass.addMethod(method);
    }

    public void batchReplaceMethod(IntrospectedTable introspectedTable, String domainObjectName,
                                   TopLevelClass daoClass) {
        Method method = new Method("batchReplace");
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(FullyQualifiedJavaType.getIntInstance());
        Parameter parameter = new Parameter(
            new FullyQualifiedJavaType("List<" + domainObjectName + ">"), "records");
        method.addParameter(parameter);
        method.addAnnotation("@Generated(\"org.mybatis.generator.api.MyBatisGenerator\")");
        method.addAnnotation("@Override");
        method.addBodyLine(" return super.batchReplace(records);");
        daoClass.addMethod(method);
    }

    public void batchInsertSelectiveMethod(IntrospectedTable introspectedTable,
                                           String domainObjectName, TopLevelClass daoClass) {
        Method method = new Method("batchInsertSelective");
        method.setVisibility(JavaVisibility.PUBLIC);
        // method.setReturnType(FullyQualifiedJavaType.getIntInstance());
        Parameter parameter = new Parameter(
            new FullyQualifiedJavaType("List<" + domainObjectName + ">"), "records");
        method.addParameter(parameter);
        method.addAnnotation("@Generated(\"org.mybatis.generator.api.MyBatisGenerator\")");
        method.addAnnotation("@Override");
        method.addBodyLine("  super.batchInsertSelective(records);");
        daoClass.addMethod(method);
    }

    public void selectPageMethod(IntrospectedTable introspectedTable,
                                           String domainObjectName, TopLevelClass daoClass) {
        if (!isApiPluginEnabled()) {
           return;
        }
        Method method = new Method("selectPage");
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(
                new FullyQualifiedJavaType(MessageFormat.format("PageInfo<{0}>", domainObjectName)));

        Parameter selectdParameter = new Parameter(new FullyQualifiedJavaType(domainObjectName + "SelectPageRequest"), "request");
        method.addParameter(selectdParameter);
        method.addAnnotation("@Generated(\"org.mybatis.generator.api.MyBatisGenerator\")");

        method.addBodyLine(
                MessageFormat.format("{0}Example example = new {0}Example();", domainObjectName));
        method.addBodyLine(
                MessageFormat.format("{0}Example.Criteria criteria = example.createCriteria();", domainObjectName));
        method.addBodyLine("return PageHelper.startPage(request.getPageNum(), request.getPageSize()).doSelectPageInfo(");
        method.addBodyLine("() -> mapper.selectByExample(example));");
        daoClass.addMethod(method);
    }

    private boolean isApiPluginEnabled() {
        try {
            Plugin pluginAggregator = context.getPlugins();

            java.lang.reflect.Field pluginsField = CompositePlugin.class.getDeclaredField("plugins");
            pluginsField.setAccessible(true);

            List<Plugin> plugins = (List<Plugin>) pluginsField.get(pluginAggregator);

            // 检查是否包含 ApiPlugin
            for (Plugin plugin : plugins) {
                if (plugin instanceof ApiPlugin) {
                    return true;
                }
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

        return false;
    }

    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }

    private boolean isExistExtFile(String targetProject, String targetPackage, String fileName) {

        File project = new File(targetProject);
        if (!project.isDirectory()) {
            return true;
        }

        StringBuilder sb = new StringBuilder();
        StringTokenizer st = new StringTokenizer(targetPackage, ".");
        while (st.hasMoreTokens()) {
            sb.append(st.nextToken());
            sb.append(File.separatorChar);
        }

        File directory = new File(project, sb.toString());
        if (!directory.isDirectory()) {
            boolean rc = directory.mkdirs();
            if (!rc) {
                return true;
            }
        }

        File testFile = new File(directory, fileName);
        return testFile.exists();
    }

    /**
     * 生成额外java文件
     */
    @Override
    public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {
        String domainObjectName = introspectedTable.getFullyQualifiedTable().getDomainObjectName();
        StringBuilder builder = new StringBuilder();
        FullyQualifiedJavaType superInterfaceType = new FullyQualifiedJavaType(
            builder.append("AbstractBaseDao<").append(domainObjectName).append(",")
                .append(domainObjectName).append("MapperExt").append(">").toString());
        FullyQualifiedJavaType mapperType = new FullyQualifiedJavaType(
            introspectedTable.getMyBatis3JavaMapperType());
        TopLevelClass daoClass = new TopLevelClass(builder.delete(0, builder.length())
            .append(mapperType.getPackageName().replace(".mapper", "").replace("domain", "dao"))
            .append(".").append(domainObjectName).append("Dao").toString());
        daoClass.setSuperClass(superInterfaceType);
        daoClass.setVisibility(JavaVisibility.PUBLIC);
        context.getCommentGenerator().addJavaFileComment(daoClass);
        FullyQualifiedJavaType domainType = new FullyQualifiedJavaType(
            introspectedTable.getBaseRecordType());
        daoClass.addImportedType(domainType);
        FullyQualifiedJavaType extType = new FullyQualifiedJavaType(
            mapperType.getPackageName() + "." + domainObjectName + "MapperExt");
        daoClass.addImportedType(extType);
        //
        FullyQualifiedJavaType daoType = new FullyQualifiedJavaType(
            "com.company.beauty.dao.AbstractBaseDao");
        daoClass.addImportedType(daoType);
        // sl4j
        FullyQualifiedJavaType slf4j = new FullyQualifiedJavaType("lombok.extern.slf4j.Slf4j");
        daoClass.addAnnotation("@Slf4j");
        daoClass.addImportedType(slf4j);
        daoClass.addImportedType(new FullyQualifiedJavaType("javax.annotation.Generated"));
        String basePage = mapperType.getPackageName().replace(".mapper", "");
        daoClass.addImportedType(new FullyQualifiedJavaType("org.apache.commons.collections4.CollectionUtils"));
        daoClass.addImportedType(new FullyQualifiedJavaType(basePage + ".model.*"));
        daoClass.addImportedType(new FullyQualifiedJavaType("java.util.List"));
        daoClass.addImportedType(new FullyQualifiedJavaType("java.util.ArrayList"));
        if (isApiPluginEnabled()) {
            daoClass.addImportedType(new FullyQualifiedJavaType("com.github.pagehelper.PageHelper"));
            daoClass.addImportedType(new FullyQualifiedJavaType("com.github.pagehelper.PageInfo"));
            daoClass.addImportedType(new FullyQualifiedJavaType(basePage.replace(".domain", ".request") +"."+domainObjectName +"SelectPageRequest"));
        }

        //@Slf4j
        //@Repository
        FullyQualifiedJavaType repository = new FullyQualifiedJavaType(
            "org.springframework.stereotype.Repository");
        daoClass.addAnnotation("@Repository");
        daoClass.addImportedType(repository);
        //@RequiredArgsConstructor(onConstructor = @__(@Autowired))
        FullyQualifiedJavaType requiredArgsConstructor = new FullyQualifiedJavaType(
            "lombok.RequiredArgsConstructor");
        daoClass.addImportedType(requiredArgsConstructor);
        // org.springframework.beans.factory.annotation.Autowired;
        FullyQualifiedJavaType autowired = new FullyQualifiedJavaType(
            "org.springframework.beans.factory.annotation.Autowired");
        daoClass.addAnnotation("@RequiredArgsConstructor(onConstructor = @__(@Autowired))");
        daoClass.addImportedType(autowired);
        //
        daoClass.addJavaDocLine("/**");
        String remarks = introspectedTable.getRemarks();
        if (StringUtility.stringHasValue(remarks)) {
            String[] remarkLines = remarks.split(System.getProperty("line.separator"));
            for (String remarkLine : remarkLines) {
                daoClass.addJavaDocLine(" * <p>" + remarkLine + "</p>");
            }
        }

        StringBuilder sb = new StringBuilder();
        sb.append(" * @author ").append(System.getProperties().getProperty("user.name"));
        daoClass.addJavaDocLine(sb.toString());
        sb.setLength(0);
        sb.append(" * ").append("@version 1.0:")
            .append(introspectedTable.getFullyQualifiedTable().getDomainObjectName())
            .append("Mapper").append(".java");
        daoClass.addJavaDocLine(sb.toString());
        sb.setLength(0);
        sb.append(" * @date ");
        sb.append(getDateString());
        daoClass.addJavaDocLine(sb.toString());
        daoClass.addJavaDocLine(" */");
        //增加方法
        geTableMethod(introspectedTable, daoClass);
        selectByIdsMethod(introspectedTable, domainObjectName, daoClass);
        insertMethod(introspectedTable, domainObjectName, daoClass);
        updateMethod(introspectedTable, domainObjectName, daoClass);
        batchInsertMethod(introspectedTable, domainObjectName, daoClass);
        batchUpdateMethod(introspectedTable, domainObjectName, daoClass);
        batchReplaceMethod(introspectedTable, domainObjectName, daoClass);
        batchInsertSelectiveMethod(introspectedTable, domainObjectName, daoClass);
        selectPageMethod(introspectedTable, domainObjectName, daoClass);

        GeneratedJavaFile generatedJavaFile = new GeneratedJavaFile(daoClass,
            context.getJavaModelGeneratorConfiguration().getTargetProject(),
            context.getProperty(PropertyRegistry.CONTEXT_JAVA_FILE_ENCODING),
            context.getJavaFormatter());

        if (isExistExtFile(generatedJavaFile.getTargetProject(),
            generatedJavaFile.getTargetPackage(), generatedJavaFile.getFileName())) {
            return super.contextGenerateAdditionalJavaFiles(introspectedTable);
        }
        List<GeneratedJavaFile> generatedJavaFiles = new ArrayList<GeneratedJavaFile>(1);
        generatedJavaFile.getFileName();
        generatedJavaFiles.add(generatedJavaFile);
        return generatedJavaFiles;
    }

    protected String getDateString() {
        return DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
    }

    @Override
    public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) {
        //重新生成代码,xml内容覆盖
        sqlMap.setMergeable(false);
        return super.sqlMapGenerated(sqlMap, introspectedTable);
    }
}